package com.zw.rpc.client;

import com.zw.rpc.Request;
import com.zw.rpc.Response;
import com.zw.rpc.ServiceDescriptor;
import com.zw.rpc.codec.Decoder;
import com.zw.rpc.codec.Encoder;
import com.zw.rpc.transport.TransportClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author zw
 */
@Slf4j
public class RemoteInvoker<T> implements InvocationHandler {

    private Class<T> clazz;

    private Encoder encoder;

    private Decoder decoder;

    private TransportSelector selector;

    public RemoteInvoker(Class<T> clazz, Encoder encoder, Decoder decoder, TransportSelector selector) {
        this.clazz = clazz;
        this.encoder = encoder;
        this.decoder = decoder;
        this.selector = selector;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Request request = new Request();
        request.setServiceDescriptor(ServiceDescriptor.from(clazz, method));
        request.setParameters(args);
        Response response = invokeRemote(request);
        if (response == null || response.getCode() != 0) {
            throw new IllegalStateException("fail to invoke remote : " + request);
        }
        return response.getData();
    }

    private Response invokeRemote(Request request) {
        Response response;
        TransportClient client = null;
        try {
            client = this.selector.select();
            byte[] requestBytes = this.encoder.encode(request);
            InputStream received = client.write(new ByteArrayInputStream(requestBytes));
            byte[] responseBytes = IOUtils.readFully(received, received.available());
            response = this.decoder.decode(responseBytes, Response.class);
        } catch (IOException e) {
            log.warn(e.getMessage(), e);
            response = new Response().setCode(1).setMessage("RpcClient get error: " + e.getClass().getName() + " : " + e.getMessage());
        } finally {
            if (client != null) {
                selector.release(client);
            }
        }
        return response;
    }
}
